/*
 * Computer Graphics:  Andrew Hanson, Indiana University
 * GLUT  Graphics Exercise Template 
 *

 cc -o template.linux template.c -lglut -lGLU -lGL -lm

 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

/* Local Graphics Definitions. Also in /l/b481/include, /l/b581/include */
#include "defs.h"

/*  double y0,y1;   math.h conflict */

/***************************************************************/
/******************** MY GLOBAL VARIABLES **********************/
/***************************************************************/

// Drawing Modes
#define RUBBER_BAND_MODE 1
#define ADDITIVE_MASKING 2
#define SUBTRACTIVE_LOGIC_OPS 3
#define SCAN_CONVERSION 4

#define LOGICAL_OP_COUNT 16
#define GAP_BETWEEN_BOXES 10

int mouseButtons = 0;

int MouseModifiers = 0;

int X=0, Y=0, dX=0, dY=0;

int main_window;
int main_height, main_width;

int myDrawingMode= RUBBER_BAND_MODE;

int rb_prev_x, rb_prev_y;
int mouse_down_x, mouse_down_y;
int down_mouse_button;

int mouse_state;

int logical_op_index_current = 10; // index of GL_XOR
int logical_op_index_previous = 10;

int first_square_x= GAP_BETWEEN_BOXES, first_square_y, second_square_x,
		second_square_y, third_square_x, third_square_y;
int box_size;

GLenum logical_operations[LOGICAL_OP_COUNT] = { GL_CLEAR, GL_SET, GL_COPY,
		GL_COPY_INVERTED, GL_NOOP, GL_INVERT, GL_AND, GL_NAND, GL_OR, GL_NOR,
		GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED, GL_OR_REVERSE,
		GL_OR_INVERTED };
char* logical_op_labels[LOGICAL_OP_COUNT] = { "GL_CLEAR", "GL_SET", "GL_COPY",
		"GL_COPY_INVERTED", "GL_NOOP", "GL_INVERT", "GL_AND", "GL_NAND",
		"GL_OR", "GL_NOR", "GL_XOR", "GL_EQUIV", "GL_AND_REVERSE",
		"GL_AND_INVERTED", "GL_OR_REVERSE", "GL_OR_INVERTED" };

// booleans
int refresh_view= GL_TRUE, logical_op_changed= GL_TRUE;

/***************************************************************/
/*********************** MY FUNCTIONS    ***********************/
/***************************************************************/

void initMyCode() {
	box_size = (main_width - (4 * GAP_BETWEEN_BOXES))/3;
	first_square_x = GAP_BETWEEN_BOXES;
	first_square_y = second_square_y = third_square_y = main_height
			- GAP_BETWEEN_BOXES;
	second_square_x = 2 * GAP_BETWEEN_BOXES + box_size;
	third_square_x = 3 * GAP_BETWEEN_BOXES + 2 * box_size;
}

void printLogicalOpLabel(char* chptr) {

	int i;
	char ch;

	glColor3f(1.0, 0.0, 0.0);
	glEnable(GL_COLOR_LOGIC_OP);
	glLogicOp(GL_XOR);

	glRasterPos2i(50, 50);
	glutBitmapCharacter(GLUT_BITMAP_9_BY_15, 'x');
	glutBitmapCharacter(GLUT_BITMAP_9_BY_15,' ');
	i = 0;
	ch = chptr[i];
	while ((ch != 0x00)&&(i<12)) {
		printf("Debug: char =[%c] [%d] ", ch, ch);
		glutBitmapCharacter(GLUT_BITMAP_9_BY_15,ch);
		i = i+1;
		ch= chptr[i];
	}

	glDisable(GL_COLOR_LOGIC_OP);
}

void drawLine(x1, y1, x2, y2)
{
	glLineWidth(5.0);

	glBegin(GL_LINE);
	glColor3f(1.0, 1.0, 0.0);
	glVertex2i(x1, y1);
	glVertex2i(x2, y2);
	glEnd();
}

void drawXORRubberBand() {

	if ((logical_op_index_current == logical_op_index_previous)
			&& (logical_op_changed == GL_TRUE)) {
		printLogicalOpLabel(logical_op_labels[logical_op_index_previous]);
		logical_op_changed = GL_FALSE;
	} else if (logical_op_changed == GL_TRUE) { //logical operations can not be changed when mouse is down
		printLogicalOpLabel(logical_op_labels[logical_op_index_previous]);
		printLogicalOpLabel(logical_op_labels[logical_op_index_current]);
		logical_op_changed = GL_FALSE;
	}

	if (mouse_state == GLUT_DOWN) {
		glEnable(GL_COLOR_LOGIC_OP);
		glLogicOp(logical_operations[logical_op_index_current]);

		drawLine(mouse_down_x, mouse_down_y, rb_prev_x, rb_prev_y);
		drawLine(mouse_down_x, mouse_down_y, X, Y);

		rb_prev_x = X;
		rb_prev_y = Y;

		glDisable(GL_COLOR_LOGIC_OP);
	}

}

void drawAdditiveModel() {

	glEnable(GL_COLOR_LOGIC_OP);
	glLogicOp(GL_XOR);

	if (refresh_view == GL_TRUE || mouseButtons == 0x1) {
		glColor3f(1.0, 0.0, 0.0);

		glRecti(first_square_x + (rb_prev_x -mouse_down_x), first_square_y
				+ (rb_prev_y - mouse_down_y), first_square_x + box_size
				+ (rb_prev_x - mouse_down_x), first_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(first_square_x + (X -mouse_down_x), first_square_y + (Y
					- mouse_down_y), first_square_x + box_size + (X
					- mouse_down_x), first_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	if (refresh_view == GL_TRUE || mouseButtons == 0x2) {
		glColor3f(0.0, 1.0, 0.0);

		glRecti(second_square_x + (rb_prev_x -mouse_down_x), second_square_y
				+ (rb_prev_y - mouse_down_y), second_square_x + box_size
				+ (rb_prev_x - mouse_down_x), second_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(second_square_x + (X -mouse_down_x), second_square_y + (Y
					- mouse_down_y), second_square_x + box_size + (X
					- mouse_down_x), second_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	if (refresh_view == GL_TRUE || mouseButtons == 0x4) {
		glColor3f(0.0, 0.0, 1.0);
		glRecti(third_square_x + (rb_prev_x -mouse_down_x), third_square_y
				+ (rb_prev_y - mouse_down_y), third_square_x + box_size
				+ (rb_prev_x - mouse_down_x), third_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(third_square_x + (X -mouse_down_x), third_square_y + (Y
					- mouse_down_y), third_square_x + box_size + (X
					- mouse_down_x), third_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	rb_prev_x = X;
	rb_prev_y = Y;

	refresh_view = GL_FALSE;
	glDisable(GL_COLOR_LOGIC_OP);

}

void drawSubtractiveModel() {
	glEnable(GL_COLOR_LOGIC_OP);
	glLogicOp(GL_XOR);
	glColor3f(1.0, 1.0, 1.0);

	if (refresh_view == GL_TRUE || mouseButtons == 0x1) {
		glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);

		glRecti(first_square_x + (rb_prev_x -mouse_down_x), first_square_y
				+ (rb_prev_y - mouse_down_y), first_square_x + box_size
				+ (rb_prev_x - mouse_down_x), first_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(first_square_x + (X -mouse_down_x), first_square_y + (Y
					- mouse_down_y), first_square_x + box_size + (X
					- mouse_down_x), first_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	if (refresh_view == GL_TRUE || mouseButtons == 0x2) {
		glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);

		glRecti(second_square_x + (rb_prev_x -mouse_down_x), second_square_y
				+ (rb_prev_y - mouse_down_y), second_square_x + box_size
				+ (rb_prev_x - mouse_down_x), second_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(second_square_x + (X -mouse_down_x), second_square_y + (Y
					- mouse_down_y), second_square_x + box_size + (X
					- mouse_down_x), second_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	if (refresh_view == GL_TRUE || mouseButtons == 0x4) {
		glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
		glRecti(third_square_x + (rb_prev_x -mouse_down_x), third_square_y
				+ (rb_prev_y - mouse_down_y), third_square_x + box_size
				+ (rb_prev_x - mouse_down_x), third_square_y - box_size
				+ (rb_prev_y - mouse_down_y));
		if (refresh_view != GL_TRUE) {
			glRecti(third_square_x + (X -mouse_down_x), third_square_y + (Y
					- mouse_down_y), third_square_x + box_size + (X
					- mouse_down_x), third_square_y - box_size + (Y
					- mouse_down_y));
		}
	}

	rb_prev_x = X;
	rb_prev_y = Y;

	refresh_view = GL_FALSE;
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glDisable(GL_COLOR_LOGIC_OP);
}

void drawLineUsingBresenhamAlgorithm(int target_x, int target_y) {
	int y = mouse_down_y, width = target_x - mouse_down_x, height = target_y
			- mouse_down_y;
	int error_term = 2 * height - width;
	int denom = error_term == 0 ? 1 : error_term;
	int x;
    glLineWidth(1.0);
	
	for (x = mouse_down_x; x <= target_x; x++) {
		
		glBegin(GL_POINTS);
		glColor3f(0.3, 0.8, 0.2);
		glVertex2f(x, y);
		glEnd();

		fprintf(stderr, " Error %d \n", error_term);
		
		
		// error graph

		glBegin(GL_LINE);
		glColor3f(1.0, 0.0, 0.0);
		glVertex2f(x, main_height/2 - 100);
		//glVertex2f(x, 60 * error_term/denom + 60);
		glVertex2f(x, error_term + 100);
		glEnd();

		if (error_term < 0) {
			error_term += 2 * height;
		} else {
			y++;
			error_term += 2 * (height - width);
		}
	}

}

void drawScanConversion() {
	if (mouseButtons == 0x1) {

		glEnable(GL_COLOR_LOGIC_OP);
		glLogicOp(GL_XOR);

		// erase the previous line
		drawLineUsingBresenhamAlgorithm(rb_prev_x, rb_prev_y);

		// draw the current line
		drawLineUsingBresenhamAlgorithm(X, Y);

		rb_prev_x = X;
		rb_prev_y = Y;

		glDisable(GL_COLOR_LOGIC_OP);
	}

}

/***************************************************************/
/*********************** MOUSE CALLBACKS ***********************/
/***************************************************************/

/* Mouse Button  UP/DOWN  or DOWN/UP transition only */
void mouse(int button, int state, int x, int y) {
	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */
	button = (1 << button);

	MouseModifiers = glutGetModifiers();
	/* bitmask with GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT */

	if (state == GLUT_DOWN)
		mouseButtons |= button;
	else
		mouseButtons &= (~(button));

	fprintf(stderr,"Comment this out: mouse buttons = [%d], modifiers = [%d] \n\
          state = %s, mouse at (%d,%d)\n",
	mouseButtons, MouseModifiers,
	state == GLUT_UP? "Just Up":"Just Down",x,y);

	mouse_state = state;

	if (state == GLUT_DOWN) {
		mouse_down_x = rb_prev_x = X;
		mouse_down_y = rb_prev_y = Y;
		down_mouse_button = mouseButtons;

		//drawLine(mouse_down_x, mouse_down_y, rb_prev_x, rb_prev_y);
	}

	if ((state == GLUT_UP) && (myDrawingMode == ADDITIVE_MASKING
			|| myDrawingMode == SUBTRACTIVE_LOGIC_OPS)) {
		if (down_mouse_button == 0x1) {
			first_square_x = first_square_x + (X - mouse_down_x);
			first_square_y = first_square_y + (Y - mouse_down_y);
		} else if (down_mouse_button == 0x2) {
			second_square_x = second_square_x + (X -mouse_down_x);
			second_square_y = second_square_y + (Y - mouse_down_y);
		} else if (down_mouse_button == 0x4) {
			third_square_x = third_square_x + (X -mouse_down_x);
			third_square_y = third_square_y + (Y - mouse_down_y);
		}
	}

	glutPostRedisplay();
}

/* Mouse motion WHILE BUTTON DOWN  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void motion(int x, int y) {
	fprintf(stderr,"Comment this out: mouse at (%d,%d)\n\
          mouse buttons = [%d], modifiers = [%d]\n",
	x,y, mouseButtons, MouseModifiers);
	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */

	switch (mouseButtons) {
	case 0x1: /* Left Mouse Button   */
		break;
	case 0x2: /* Middle Mouse Button */
		break;
	case 0x4: /* Right Mouse Button  */
		break;
	}
	glutPostRedisplay();

}

/* Mouse motion WHILE ALL BUTTONS UP  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void passive(int x, int y) {
	X = x;
	Y = main_height - y - 1;

	glutPostRedisplay();
}

/* Handle Keyboard */
void keyboard(unsigned char key, int x, int y) {
	/*  Option: To force all lower case */
	/* if (key >= 'A' && key <= 'Z') key = (key-'A') + 'a'; */

	mouse_down_x = mouse_down_y = rb_prev_x = rb_prev_y = 0;

	switch (key) {

	case '1':
		/* Rubber band Mode */
		myDrawingMode = RUBBER_BAND_MODE;
		refresh_view = GL_TRUE;
		fprintf(stdout,"Rubber Band Mode \n");
		break;
	case '2':
		/* Additive Color Model with Masking */
		myDrawingMode = ADDITIVE_MASKING;
		refresh_view = GL_TRUE;
		initMyCode();
		fprintf(stdout,"Additive Color Model with Masking \n");
		break;
	case '3':
		/* Subtractive model with logical ops */
		myDrawingMode = SUBTRACTIVE_LOGIC_OPS;
		refresh_view = GL_TRUE;
		initMyCode();
		fprintf(stdout,"Subtractive model with logical ops \n");
		break;
	case '4':
		/*  */
		myDrawingMode = SCAN_CONVERSION;
		refresh_view = GL_TRUE;
		initMyCode();
		fprintf(stdout,"Scan conversion \n");
		break;
	case 'r':
		/* Refresh View */
		refresh_view = GL_TRUE;
		logical_op_index_current = 10;
		logical_op_index_previous = 10;
		logical_op_changed = GL_TRUE;
		mouse_down_x = mouse_down_y = rb_prev_x = rb_prev_y = 0;
		fprintf(stdout,"Refreshing View \n");
		break;
	case ' ':
		/* Change Logical Operation */
		logical_op_index_previous = logical_op_index_current;
		logical_op_index_current = (logical_op_index_current + 1)
				% LOGICAL_OP_COUNT;
		logical_op_changed = GL_TRUE;
		fprintf(stdout,"\nLogical Operation changed from %s to %s \n", logical_op_labels[logical_op_index_previous], logical_op_labels[logical_op_index_current]);
		break;
	case 'q':
	case 27: /* 27 -> ESC */
		fprintf(stderr,"Normal Exit.\n");
		exit(EXIT_SUCCESS);
		break;
	default:
		fprintf(stderr,"Unhandled key: %c [%d] \n",key,key);
	}

	glutPostRedisplay();
}

/***************************************************************/
/************************** INIT GL ****************************/
/***************************************************************/

void initGL() {
	glClearColor(0.0, 0.0, 0.0, 0.0);

}

/***************************************************************/
/************************ DRAW STUFF ***************************/
/***************************************************************/

void drawObjs() {

	switch (myDrawingMode) {
	case RUBBER_BAND_MODE: /* pixel manipulations */
		/* .... more code here .... */

		if (refresh_view == GL_TRUE) {
			glClearColor(0.0, 0.0, 0.0, 0.0);
			glClear(GL_COLOR_BUFFER_BIT);
			//glColor3f(0.6, 0.8, 0.4);
			glColor3f(1.0, 0.0, 1.0);
			glRecti(main_width/4, 3*main_height/4, 3*main_width/4, main_height
					/4);
			refresh_view = GL_FALSE;
		}
		drawXORRubberBand();
		break;
	case 2: /* Additive Model */
		if (refresh_view == GL_TRUE) {
			glClearColor(0.0, 0.0, 0.0, 0.0);
			glClear(GL_COLOR_BUFFER_BIT);
		}
		drawAdditiveModel();
		break;
	case 3: /* Additive Model */
		if (refresh_view == GL_TRUE) {
			glClearColor(1.0, 1.0, 1.0, 0.0);
			glClear(GL_COLOR_BUFFER_BIT);
		}
		drawSubtractiveModel();
		break;
	case 4:
		if (refresh_view == GL_TRUE) {
			glClearColor(0.0, 0.0, 0.0, 0.0);
			glClear(GL_COLOR_BUFFER_BIT);
			refresh_view = GL_FALSE;

			// draw the center line to separate the window
			glBegin(GL_LINE);
			glColor3f(0.2, 0.4, 0.6);
			glVertex2i(0, main_height/2);
			glVertex2i(main_width, main_height/2);
			glEnd();

		} else {
			drawScanConversion();
		}
		break;
	default:
		fprintf(stderr,"Unhandled drawing mode: %d \n",myDrawingMode);
	}

}

/***************************************************************/
/************************ GLUT STUFF ***************************/
/***************************************************************/

void reshape(int w, int h) {
	fprintf(stderr,"Comment this out: reshape(%d,%d) \n",w,h);
	w = Max(w,128);
	h = Max(h,128);
	main_height = h;
	main_width = w;
	glutReshapeWindow(w, h);
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, (double)w-1, 0.0, (double)h-1);

	glMatrixMode(GL_MODELVIEW);

	refresh_view = GL_TRUE;

	initMyCode();

}

void visibility(int state) {
	/* May also want to check PARTIAL DAMAGE that
	 does not result in a call to the visibility callback:
	 if (glutLayerGet(GLUT_NORMAL_DAMAGED)) {
	 redraw = TRUE; }
	 */
	fprintf(stderr,"Comment this out: visibility: %s\n",
	(state==GLUT_VISIBLE)?"visible":"invisible");
	if (state == GLUT_VISIBLE) {
		glutPostRedisplay();
	}
}

void display(void) {
	fprintf(stderr,"Comment this out: display\n");

	drawObjs();

	glFlush();
}

/***************************************************************/
/**************************** MAIN *****************************/
/***************************************************************/

int main(int argc, char **argv) {
	/* GLUT Initialization */
	glutInit(&argc, argv);
	glutInitWindowSize(500, 500);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

	/*   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);  */

	/* Create a new window and give a title to it */
	main_window = glutCreateWindow(" :: PS2 - Eran Chinthaka ::");

	/* Initialize OpenGL stuff */
	initGL();

	/* Post Event Handlers */
	glutReshapeFunc(reshape);
	glutVisibilityFunc(visibility);
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutPassiveMotionFunc(NULL);

	glutKeyboardFunc(keyboard);
	fprintf(stderr,"Press ESC or 'q' to Exit.\n");

	glutIdleFunc(NULL);

	glutMainLoop();

	return (EXIT_SUCCESS);
}
